package com.jse.io.nio.network.new1.mine.test;

import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * OP_ACCEPT、OP_READ == Read
OP_CONNECT、OP_WRITE == Write
	读/写方式注册，避免同一种类型的一起注册了，
	
	参考资料：《NIO trick and trap》淘宝PPT
		http://www.ibm.com/developerworks/cn/java/j-lo-javaio/
		
		
		参考：https://my.oschina.net/javagg/blog/3361
		http://ifeve.com/selectors/
		搜索 java nio interestOps
		
	写事件bug参考：http://bugs.java.com/view_bug.do?bug_id=4469394
 * @author zenghua233
 *
 */
public class Server extends Thread {
	private Selector selector;
	
	@Override
	public void run() {
		try {
			this.selector = Selector.open();
			ServerSocketChannel server = ServerSocketChannel.open();
			server.configureBlocking(false);
			//listener address（产生listenerfd）
			server.bind(new InetSocketAddress("127.0.0.1", 999));
			server.register(selector, SelectionKey.OP_ACCEPT);
			
			while (true) {
				selector.select();
				
				Set<SelectionKey> keys = selector.selectedKeys();
				System.out.println(keys.size());
				Iterator<SelectionKey> iterator = keys.iterator();
				while(iterator.hasNext()) {
					SelectionKey connKey = iterator.next();
					
					//当前key使用完了就要从selector池中删掉
					iterator.remove();
					System.out.println("server key : " + keys.size());
					//不合法就跳出当前模块
					if (!connKey.isValid()) {
						continue;
					}
					
					//建立好连接就使用状态机机制，这步表示准备好连接了，下一步是读还是写要写入到Selector池中
					if (connKey.isAcceptable()) {
						this.accept(connKey);
					}
					
					if (connKey.isWritable()) {
						this.write(connKey);
					}
					
					//如果注册了没有执行，服务器会一直循环下去（remove也没有作用，从新获取selectionkey以前未完成的还在？？TODO）
					if (connKey.isReadable()) {
						this.read(connKey);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 为什么服务器形式的客户端在轮询的时候，不能注册OP_READ（客户端是当个线程形式就可以，参考SocketServerExample）
	 * @param connKey
	 */
	public void accept(SelectionKey connKey) {
		try {
			//获取连接key的通道（key与channel绑定好了的，参考regist方法，第一步accept的时候就绑定了）
			ServerSocketChannel s = (ServerSocketChannel) connKey.channel();
			//获取连接的文件描述
			SocketChannel connfd = s.accept();
			
			Socket socket = connfd.socket();
	        SocketAddress remoteAddr = socket.getRemoteSocketAddress();
	        System.out.println("Connected to: " + remoteAddr);
			
			
//			connfd.configureBlocking(false);
//			//服务器端自循环（启动服务端的时候，查看进程都是一对一对的）
//			connfd.register(this.selector, SelectionKey.OP_READ);
	        
//	        ByteBuffer buffer = ByteBuffer.allocate(100);
//	        connfd.read(buffer);
////			buffer.
//			System.out.println("server读取到客户端数据 : " + new String(buffer.array()));
//			buffer.flip();//清空缓存
	        
			
			//这里注册了读写时间后，会一直死循环（OP_WRITE直接死循环，OP_READ可以读，但是客户端段了抛异常） TODO
			connfd.configureBlocking(false);
////			//服务器端自循环（启动服务端的时候，查看进程都是一对一对的）
			connfd.register(this.selector, SelectionKey.OP_READ);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void read(SelectionKey connKey) {
		try {
			SocketChannel connfd = (SocketChannel) connKey.channel();
			ByteBuffer buffer = ByteBuffer.allocate(100);
			connfd.read(buffer);
//			buffer.
			System.out.println("server读取到客户端数据 : " + new String(buffer.array()));
			buffer.flip();//清空缓存
			
			connfd.configureBlocking(false);
////		//服务器端自循环（启动服务端的时候，查看进程都是一对一对的）
			connfd.register(this.selector, SelectionKey.OP_WRITE);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void write(SelectionKey connKey) {
		try {
			SocketChannel connfd = (SocketChannel) connKey.channel();
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			//写到阻塞队列（防止并发下数据丢失）
			connfd.write(ByteBuffer.wrap("hello client".getBytes()));
			
//			connfd.configureBlocking(false);
////		//服务器端自循环（启动服务端的时候，查看进程都是一对一对的）
//			connfd.register(this.selector, SelectionKey.OP_READ);
//			connKey.cancel();
			//如果有特别需求服务器才写，不然忘记注销写时间，cpu很有可能100%
//			connKey.interestOps(SelectionKey.OP_WRITE);
			
//			int writeOps = connKey.interestOps();
//			
//			connKey.interestOps(connKey.interestOps() & ~readyOps);
			
			int readyOps = connKey.readyOps();
			System.out.println(readyOps);
			System.out.println(connKey.isWritable());
			//写完数据把写事件注销掉
			connKey.interestOps(connKey.interestOps() & ~readyOps);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new Server().start();
	}
}
